﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpExt.Text
{
    /// <summary>
    /// Deve supportare le opzioni espresse nel seguente modo
    /// 
    ///     -o
    ///     -option
    ///     --o
    ///     --option
    ///     /o
    ///     /option
    /// 
    /// Inoltre deve poter prevedere:
    /// 
    ///     --option value
    ///     --option=value
    ///     --option:value
    /// 
    /// Inoltre deve essre in grado i ritornare tutti i parametri
    /// non associati a nessuna opzione
    /// 
    /// Update: deve supportare la presenza di piu' parametri con lo stesso nome:
    /// 
    ///     -D v1 -D v2 ...
    /// 
    /// </summary>
    public class CommandLine
    {
        #region Factory Methods 

        public static void Parse(String[] args)
        {
            _cl.parse(args);
        }

        public static bool HasOption(CommandLineOption option)
        {
            return _cl.hasOption(option);
        }

        public static String OptionValue(CommandLineOption option)
        {
            return _cl.optionValue(option);
        }

        public static int OptionValue(CommandLineOption option, int value)
        {
            return _cl.optionValue(option, value);
        }

        public static bool OptionValue(CommandLineOption option, bool value)
        {
            return _cl.optionValue(option, value);
        }

        public static String OptionValue(CommandLineOption option, String value)
        {
            return _cl.optionValue(option, value);
        }

        public static int IntegerValue(CommandLineOption option)
        {
            return _cl.integerValue(option);
        }

        public static void ShowOptions()
        {
            _cl.showOptions();
        }


        public static String Parameter
        {
            get
            {
                return _cl.parameter;
            }
        }

        public static String[] Parameters
        {
            get
            {
                return _cl.parameters;
            }
        }

        public static String[] Args
        {
            get
            {
                return _cl.args;
            }
        }

        public static String At(int index)
        {
            if (index >= _cl.parameters.Length)
                return String.Empty;
            else
                return _cl.parameters[index];
        }

        #endregion

        #region Private Data

        private static CommandLine _cl = new CommandLine();

        private String[] _args;
        private List<CommandLineOption> _options = new List<CommandLineOption>();

        private Dictionary<CommandLineOption, String> _values = new Dictionary<CommandLineOption, String>();
        private List<String> _parameters = new List<String>();

        #endregion

        #region Constructor

        public CommandLine()
        {
        }

        #endregion

        #region Options Handling

        internal static void add(CommandLineOption option)
        {
            _cl.addOption(option);
        }


        void addOption(CommandLineOption option)
        {
            _options.Add(option);
        }

        bool hasOption(CommandLineOption option)
        {
            return _values.ContainsKey(option);
        }

        String optionValue(CommandLineOption option)
        {
            String value;
            if(_values.TryGetValue(option, out value))
                return value;
            else
                return null;
        }

        int optionValue(CommandLineOption option, int value)
        {
            String svalue;
            if (_values.TryGetValue(option, out svalue))
                return int.Parse(svalue);
            else
                return value;
        }

        bool optionValue(CommandLineOption option, bool value)
        {
            String svalue;
            if (_values.TryGetValue(option, out svalue))
                return bool.Parse(svalue);
            else
                return value;
        }

        String optionValue(CommandLineOption option, String value)
        {
            String svalue;
            if (_values.TryGetValue(option, out svalue))
                return svalue;
            else
                return value;
        }

        int integerValue(CommandLineOption option)
        {
            int value;
            String svalue = OptionValue(option);
            if(int.TryParse(svalue, out value))
                return value;
            else
                return 0;
        }


        String parameter
        {
            get
            {
                return _parameters.Count > 0 ? _parameters[0] : null;
            }
        }

        String[] parameters
        {
            get
            {
                return _parameters.ToArray();
            }
        }

        String[] args
        {
            get { return _args; }
        }


        void parse(String[] args)
        {
            _args = args;
            
            for(int i=0; i<args.Length; ++i)
            {
                String arg = args[i];
                if(isOption(arg))
                {
                    CommandLineOption option = parseOption(arg);
                    if (option.HasValue)
                    {
                        _values.Add(option, args[++i]);
                    }
                    else
                    {
                        _values.Add(option, "true");
                    }
                }
                else
                {
                    _parameters.Add(arg);
                }
            }
        }

        #endregion

        #region Implementation

        private bool isOption(String arg)
        {
            return arg.StartsWith("-") && arg.Length == 2 || arg.StartsWith("--") || arg.StartsWith("/");
        }

        private CommandLineOption parseOption(String arg)
        {
            if (arg.StartsWith("--"))
                arg = arg.Substring(2);
            else
                arg = arg.Substring(1);

            if(arg.Length == 1)
            {
                foreach(CommandLineOption option in _options)
                {
                    if (option.ShortName.Equals(arg))
                        return option;
                }

            }
            else
            {
                foreach (CommandLineOption option in _options)
                {
                    if (option.LongName.Equals(arg))
                        return option;
                }

            }

            throw new ArgumentException(String.Format("Option {0} unknown", arg));
        }

        private void showOptions()
        {
            if(_options.Count == 0)
                return;

            Console.WriteLine("Options:");
            foreach (CommandLineOption option in _options)
            {
                Console.WriteLine("    {0}", option.ToString());
            }
        }

        #endregion
    }

    public class CommandLineOption
    {
        #region Private Data

        private String _longName;
        private String _shortName;
        private bool _hasValue;
        private String _valueName;
        private String _description;

        #endregion

        #region Constructor

        public CommandLineOption(String longName)
        {
            init(longName, longName[0], false, null);
        }

        public CommandLineOption(String longName, char shortName)
        {
            init(longName, shortName, false, null);
        }

        public CommandLineOption(String longName, bool hasValue)
        {
            init(longName, longName[0], hasValue, null);
        }

        public CommandLineOption(String longName, char shortName, bool hasValue)
        {
            init(longName, shortName, hasValue, null);
        }

        #endregion

        #region Properties

        public CommandLineOption AddDescription(String desc)
        {
            _description = desc;
            return this;
        }

        public String ShortName
        {
            get
            {
                return _shortName;
            }
        }

        public String LongName
        {
            get
            {
                return _longName;
            }
        }

        public bool HasValue
        {
            get
            {
                return _hasValue;
            }
        }

        public String ValueName
        {
            get
            {
                return _valueName;
            }
            set
            {
                _hasValue = true;
                _valueName = value;
            }
        }

        public String Description
        {
            get
            {
                return _description;
            }
            set
            {
                _description = value;
            }
        }

        public new String ToString()
        {
            if(_hasValue)
                return String.Format("-{0} | --{1} <{3}>: {2}", ShortName, LongName, Description, _valueName);
            else
                return String.Format("-{0} | --{1}: {2}", ShortName, LongName, Description);
        }

        #endregion

        #region Implementation

        private void init(String longName, char shortName, bool hasValue, String valueName)
        {
            _longName = longName;
            _shortName = shortName.ToString();
            _hasValue = hasValue;
            _valueName = String.IsNullOrEmpty(valueName) ? "value" : valueName;

            CommandLine.add(this);
        }

        #endregion

    }
}
